#include "mpx_supt.h"
#include "R2.h"
#include "R3.h"
#include <stdlib.h>

pq ready, blocked, suspended, suspended_blocked;

void pq_init_specialized(){
	pq_init(&ready, compare_nodes_priority, delete_data, find);
	pq_init(&blocked, compare_nodes_fifo, delete_data, find);
	pq_init(&suspended, compare_nodes_priority, delete_data, find);
	pq_init(&suspended_blocked, compare_nodes_fifo, delete_data, find);
}

int compare_nodes_priority(void *pcb1, void *pcb2){
	ppcb a = (ppcb) pcb1;
	ppcb b = (ppcb) pcb2;
	
	return (
		a->priority > b->priority || (
			a->priority == b->priority && a->added < b->added
		) ?
		TRUE :
		FALSE
	);
}

int compare_nodes_fifo(void *pcb1, void *pcb2){
	ppcb a = (ppcb) pcb1;
	ppcb b = (ppcb) pcb2;
	
	return (a->added < b->added ? TRUE : FALSE);
}

int delete_data(void *block){

	ppcb block_temp = (ppcb) block;

	if(block_temp->load != NULL){
		sys_free_mem(block_temp->load);
	}
	
	sys_free_mem(block);
	
	return OK;
}

int find(void *block, int argc, void **argv){
	char *nameCompare = ((ppcb)block)->name;
	
	argc = argc; // TurboC wants all args used >_>
	
	return (strcmp(nameCompare, (char *)argv[0]) == 0 ? TRUE : FALSE);
}

void pq_free_specialized(){
	pq_free(&ready);
	pq_free(&blocked);
	pq_free(&suspended);
	pq_free(&suspended_blocked);
}

// Block_PCB
// Places the PCB in the blocked queue. 
// Paramaters: none
// Returns: nothing. 
// This aglorithm makes use of Find_PCB, Remove_PCB, and the Insert_PCB functions.
void Block_PCB(int argc, char **argv ){
	char pcb_Name[32];
	int name_size = 32;
	ppcb myPCB;

	printf("Enter the process name to block\n");
	printf("> ");
	if(get_string_strip_endline(pcb_Name, name_size) == ERR_SUP_RDFAIL){
		printf("\n\tError reading the input.\n\n");
		return;
	}
	myPCB = Find_PCB(pcb_Name);

	if(myPCB == NULL)
	{
		printf("Process %s not found.", pcb_Name);
	}
	else
	{
		Remove_PCB(myPCB);
		Insert_PCB(BLOCKED, myPCB);
		printf("Process %s was successfully blocked", pcb_Name);
	}
	argc = argc;
	argv = argv;
}

// Unblock_PCB
// Places the process in ready queue. 
// Paramaters: none
// Returns: nothing.
// This aglorithm makes use of the Insert_PCB, Remove_PCB, and the Find_PCB functions. 
void Unblock_PCB(int argc, char **argv){

	char pcb_Name[32];
	int name_size = 32;
	ppcb myPCB;

	printf("Enter the process name to unblock\n");
	printf("> ");
	if(get_string_strip_endline(pcb_Name, name_size) == ERR_SUP_RDFAIL){
		printf("\n\tError reading the input.\n\n");
		return;
	}
	myPCB = Find_PCB(pcb_Name);

	if(myPCB == NULL)
	{
		printf("Process %s not found.", pcb_Name);
	}
	else
	{
		Remove_PCB(myPCB);
		Insert_PCB(READY, myPCB);
		printf("Process %s was successfully moved to the ready queue.", pcb_Name);
	}
	argc = argc;
	argv = argv;
}

// Suspended_PCB
// Places the process in the suspended queue. 
// Paramaters: none
// Returns: nothing.
// This aglorithm makes use of the Insert_PCB, Remove_PCB, and the Find_PCB functions. 

void Suspend_PCB(int argc, char **argv){

	char pcb_Name[32];
	int name_size = 32;
	ppcb myPCB;

	printf("Enter the process name to suspend\n");
	printf("> ");
	
	if(get_string_strip_endline(pcb_Name, name_size) == ERR_SUP_RDFAIL){
		printf("\n\tError reading the input.\n\n");
		return;
	}

	myPCB = Find_PCB(pcb_Name);

	if(myPCB == NULL)
	{
		printf("Process %s not found.", pcb_Name);
	}
	else
	{
		Remove_PCB(myPCB);
		Insert_PCB(SUSPENDED, myPCB);
		printf("Process %s was successfully moved to the suspended queue.", pcb_Name);
	}
	argc = argc;
	argv = argv;
}

// Resume_PCB
// Removes the process from the suspended state. 
// Paramaters: none
// Returns: nothing.
// This aglorithm retrieves user input then uses the Find_PCB, Insert_PCB, and Remove_PCB.
void Resume_PCB(int argc, char **argv){

	char pcb_Name[32];
	int name_size = 32;
	ppcb myPCB;

	printf("Enter the process name to resume\n");
	printf("> ");
	if(get_string_strip_endline(pcb_Name, name_size) == ERR_SUP_RDFAIL){
		printf("\n\tError reading the input.\n\n");
		return;
	}

	myPCB = Find_PCB(pcb_Name);

	if(myPCB == NULL)
	{
		printf("Process %s not found.", pcb_Name);
	}
	else
	{
		Remove_PCB(myPCB);
		Insert_PCB(READY, myPCB);
		printf("Process %s was successfully moved to the ready queue.", pcb_Name);
	}
	 argc = argc;
	 argv = argv;
}


// Allocate_PCB
// Allocates the memory for a new PCB.
// Paramaters: none
// Returns: pointer to the new PCB
// This aglorithm uses the MPX support function sys_alloc_mem.
ppcb Allocate_PCB()
{
	int size = sizeof(pcb);
	ppcb pcb_ptr;
	pcb_ptr = sys_alloc_mem(size);
	return(pcb_ptr);
}


// Create_PCB 
// Allocates and sets a new PCB. The PCB then is inserted into the ready queue.
// Paramaters: none
// Returns: nothing.
// This aglorithm uses the Setup_PCB function and the Insert_PCB function.
void Create_PCB(int argc, char **argv)
{
	char name_Process[PCB_NAME_SIZE];
	int process_size = PCB_NAME_SIZE;
	char pcb_class[PCB_NAME_SIZE];
	int class_size = PCB_NAME_SIZE;
	int pcb_klass;
	char pcb_priority[PCB_NAME_SIZE];
	int priority_size = PCB_NAME_SIZE;
	int pcb_prior;
	ppcb new_block;
	int error_code;
	
	argc = argc;
	argv = argv;
	
	printf("Enter the name of the process\n");
	error_code = get_string_strip_endline(name_Process, process_size);
	
	if (error_code == ERR_SUP_RDFAIL) {
		printf("\n\tError: Reading the input failed...\n\n");
		return;
	}
	
	printf("Enter the PCB class\n");
	error_code = get_string_strip_endline(pcb_class, class_size);
	
	if (error_code == ERR_SUP_RDFAIL) {
		printf("\n\tError: Reading the input failed...\n\n");
		return;
	}
	
	if (
		numberp(pcb_class, class_size) == FALSE || (
			(pcb_klass = atoi(pcb_class)) != SYSTEM &&
			pcb_klass != APPLICATION
		)
	) {
		printf(
			"\n\tError: The class must be either 1 (System) or 2 "
			"(Application).\n\n"
		);
		return;
	}
	
	printf("Enter the priority of the PCB\n");
	error_code = get_string_strip_endline(pcb_priority, priority_size);
	
	if (error_code == ERR_SUP_RDFAIL) {
		printf("\n\tError: Reading the input failed...\n\n");
		return;
	}
	
	if (
		numberp(pcb_priority, priority_size) == FALSE ||
		(pcb_prior = atoi(pcb_priority)) < -128 ||
		pcb_prior > 127
	) {
		printf(
			"\n\tError: The priority must be at least -128 and at most 127.\n\n"
		);
		return;
	}
	
	new_block = Setup_PCB(name_Process, pcb_klass, pcb_prior);
	Insert_PCB(new_block->state, new_block);
}


// Delete_PCB
// Removes an existing PCB. The memory associated with that PCB is freed. 
// Paramaters: none
// Returns: nothing.
// This aglorithm uses the Remove_PCB function and the Free_PCB function.
void Delete_PCB(int argc, char **argv)
{
	char name_PCB[32];
	int name_size = 32;
	ppcb pcbToDelete;
	
	argc = argc;
	argv = argv;
	
	printf("Enter the name of the PCB to be deleted\n");
	sys_req(READ, TERMINAL, name_PCB, &name_size);
	pcbToDelete = Find_PCB(name_PCB);
	Remove_PCB(pcbToDelete);
	Free_PCB(pcbToDelete);
}

// Free_PCB
// Frees the memory asociated with a PCB.
// Paramaters: pointer to the PCB.
// Returns: nothing.
// This aglorithm uses the MPX support function sys_free_mem.
void Free_PCB(ppcb ptr)
{
	delete_data((void *)ptr);
} 

// Find_PCB
// Searches all queues for PCB with a given name.
// Paramaters: Name of PCB.
// Returns: pointer to the PCB
// This aglorithm checks all four queues: Ready, Blocked, Suspended Ready, and Suspended Blocked.
// Note, return value of null could mean name invalid or not found
ppcb Find_PCB(char *pname) {

	ppcb temp = NULL;
	
	if(pq_find(&ready, 1, (void **)&pname, (void **)&temp) == OK)
	{
		return temp;	
	}
	else if (pq_find(&blocked, 1, (void **)&pname, (void **)&temp) == OK)
	{
		return temp;
	}
	else if (pq_find(&suspended, 1, (void **)&pname, (void **)&temp) == OK)
	{
		return temp;
	}
	else if (
		pq_find(&suspended_blocked, 1, (void **)&pname, (void **)&temp) == OK
	) {
		return temp;
	}
	else
	{
		return NULL;
	}
}

// Set_Priority
// Changes the position of the PCB in it's queue based on it's new priority.
// Paramaters: none
// Returns: nothing.
// This aglorithm finds the PCB using the Find_PCB command, removes it from its
// current position using the Remove_PCB command, then inserts it into its new position
// using the Insert_PCB command. 
void Set_Priority(int argc, char **argv) {

	ppcb temp;
	int priority = atoi(argv[1]);

	if (argc != 2) printf("Invalid number of arguments. Usage: set_priority process_name priority.\n");

	temp = Find_PCB(argv[0]);
	
	if (temp == NULL) {
		printf("Process does not exist.");
		return;	
	}
	
	if (priority < -128 || priority > 127) {
		printf("Priority should be in the range -128 to 127\n");
		return;
	}
	
	temp->priority = priority;	
	Remove_PCB(temp);
	Insert_PCB(temp->state, temp);
	printf("Priority was successfully set for process %s to %d.\n", argv[0], priority);
}

// Show_PCB
// Shows a particular PCB.
// Paramaters: none
// Returns: nothing.
// This aglorithm retrieves input from the user to print the PCB's information.

void Show_PCB (int argc, char **argv) {
	char pcb_Name[32];
	int name_size = 32;
	ppcb myPCB;

	argc = argc;
	argv = argv;

	printf("Enter the process name to show\n");
	printf("> ");
	if(get_string_strip_endline(pcb_Name, name_size) == ERR_SUP_RDFAIL){
		printf("\n\tError reading the input.\n\n");
		return;
	}

	myPCB = Find_PCB(pcb_Name);
	print_pcb(myPCB);
}

// Show_All
// Shows all processes in all queues. 
// Paramaters: none
// Returns: nothing.
// This aglorithm calls each of the other show commands. 
void Show_All(int argc, char **argv) {
	argc = argc;
	argv = argv;
	
	Show_Ready(0, NULL);
	Show_Blocked(0, NULL);
	Show_Suspended(0, NULL);
	Show_Suspended_Blocked(0, NULL);
}

// Show_Ready
// Shows all processes in the ready state. 
// Paramaters: none
// Returns: nothing.
// This aglorithm loops through the ready queue to print each process. 
void Show_Ready(int argc, char **argv) {
	int   errorState;
	int   written = 0;
	ppcb  temp;
	short pause = 0;
	
	argc = argc;
	argv = argv;
	
	errorState = pq_start_iteration(&ready);
	if(errorState == OK){
		printf("Processes in the Ready Queue:\n");
		while(pq_get_next(&ready, (void **)&temp) == OK) {
			// If a specific number of lines have been printed, pause the
			// printing so the user can catch up.
			if (pause) {
				system("pause");
				gotoxy(1, wherey() - 2);
				printf("                               ");
				gotoxy(1, wherey());
				pause = 0;
			}
			
			// Write the PCB's information to the terminal.
			print_pcb(temp);
			
			// If a specific number of lines have been printed, set the pause
			// flag.
			if ((++written % DISPLAY_AT_A_TIME) == 0) {
				pause = 1;
			}
		}
	} 
	else if(errorState == ERR_PQUEUE_EMPTYQUEUE){
		printf("There are no processes currently in the Ready Queue\n");
	}
	else{
		printf("Out of memory\n");
	}

}

// Show_Blocked
// Shows all processes in the blocked state. 
// Paramaters: none
// Returns: nothing.
// This aglorithm loops through the blocked queue to print each process. 
void Show_Blocked(int argc, char **argv) {
	int   errorState;
	int   written = 0;
	ppcb  temp;
	short pause = 0;
	
	argc = argc;
	argv = argv;
	
	errorState = pq_start_iteration(&blocked);
	if(errorState == OK){
		printf("Processes in the Blocked Queue:\n");
		while(pq_get_next(&blocked, (void **)&temp) == OK) {
			// If a specific number of lines have been printed, pause the
			// printing so the user can catch up.
			if (pause) {
				system("pause");
				gotoxy(1, wherey() - 2);
				printf("                               ");
				gotoxy(1, wherey());
				pause = 0;
			}
			
			// Write the PCB's information to the terminal.
			print_pcb(temp);
			
			// If a specific number of lines have been printed, set the pause
			// flag.
			if ((++written % DISPLAY_AT_A_TIME) == 0) {
				pause = 1;
			}
		}
	} 
	else if(errorState == ERR_PQUEUE_EMPTYQUEUE){
		printf("There are no processes currently in the Blocked Queue\n");
	}
	else{
		printf("Out of memory\n");
	}
}

// Show_Suspended
// Shows all processes in the suspended ready state. 
// Paramaters: none
// Returns: nothing.
// This aglorithm loops through the suspended ready queue to print each process. 

void Show_Suspended(int argc, char **argv){
	int   errorState;
	int   written = 0;
	ppcb  temp;
	short pause = 0;
	
	argc = argc;
	argv = argv;
	
	errorState = pq_start_iteration(&suspended);
	if(errorState == OK){
		printf("Processes in the Suspended Queue:\n");
		while(pq_get_next(&suspended, (void **)&temp) == OK) {
			// If a specific number of lines have been printed, pause the
			// printing so the user can catch up.
			if (pause) {
				system("pause");
				gotoxy(1, wherey() - 2);
				printf("                               ");
				gotoxy(1, wherey());
				pause = 0;
			}
			
			// Write the PCB's information to the terminal.
			print_pcb(temp);
			
			// If a specific number of lines have been printed, set the pause
			// flag.
			if ((++written % DISPLAY_AT_A_TIME) == 0) {
				pause = 1;
			}
		}
	} 
	else if(errorState == ERR_PQUEUE_EMPTYQUEUE){
		printf("There are no processes currently in the Suspended Queue\n");
	}
	else{
		printf("Out of memory\n");
	}
}

// Show_Suspended_Blocked
// Shows all processes in the suspended blocked state. 
// Paramaters: none
// Returns: nothing.
// This aglorithm loops through the suspended blocked queue to print each process. 
void Show_Suspended_Blocked(int argc, char **argv){
	int   errorState;
	int   written = 0;
	ppcb  temp;
	short pause = 0;
	
	argc = argc;
	argv = argv;
	
	errorState = pq_start_iteration(&suspended_blocked);
	if(errorState == OK){
		printf("Processes in the Suspended_Blocked Queue:\n");
		while(pq_get_next(&suspended_blocked, (void **)&temp) == OK) {
			// If a specific number of lines have been printed, pause the
			// printing so the user can catch up.
			if (pause) {
				system("pause");
				gotoxy(1, wherey() - 2);
				printf("                               ");
				gotoxy(1, wherey());
				pause = 0;
			}
			
			// Write the PCB's information to the terminal.
			print_pcb(temp);
			
			// If a specific number of lines have been printed, set the pause
			// flag.
			if ((++written % DISPLAY_AT_A_TIME) == 0) {
				pause = 1;
			}
		}
	} 
	else if(errorState == ERR_PQUEUE_EMPTYQUEUE){
		printf("There are no processes currently in the Suspended_Blocked Queue\n");
	}
	else{
		printf("Out of memory\n");
	}
}

// Insert_PCB
// Inserts PCB into the correct queue.
// Paramaters: new state and the pointer to PCB.
// Returns: nothing.
// This aglorithm uses a switch case to determine which queue to insert the PCB into.
void Insert_PCB(int newState, ppcb pcbToInsert){
	pcbToInsert->state = newState;
	pcbToInsert->added = clock();

	if(newState == READY){
		pq_enqueue(&ready, (void *)pcbToInsert);
	}
	else if(newState == BLOCKED){
		pq_enqueue(&blocked, (void *)pcbToInsert);
	}
	else if(newState == SUSPENDED){
		pq_enqueue(&suspended, (void *)pcbToInsert);
	}
	else if(newState == SUSPENDED_BLOCKED){
		pq_enqueue(&suspended_blocked, (void *)pcbToInsert);
	}
	else{
		printf("The destination of the process is invalid\n");
	}
}

// Remove_PCB
// Removes the PCB from it's current queue. 
// Paramaters: pointer to pcb.
// Returns: nothing.
// This aglorithm makes use of the Find_PCB method. 
void Remove_PCB(ppcb pcbToRemove){

	int state = pcbToRemove->state;

	if(state == READY){
		pq_remove(&ready, (void *)pcbToRemove);
	}
	else if(state == BLOCKED){
		pq_remove(&blocked, (void *)pcbToRemove);
	}
	else if(state == SUSPENDED){
		pq_remove(&suspended, (void *)pcbToRemove);
	}
	else if (state == SUSPENDED_BLOCKED){
		pq_remove(&suspended_blocked, (void *)pcbToRemove);
	}
	else{
		printf("The state assoicated with this process is incorrect\n");
	}
}

// Setup_PCB
// Initializes a PCB's content.
// Paramaters: name, priority, class
// Returns: pointer to the PCB
// This aglorithm uses the Allocate_PCB function. 
ppcb Setup_PCB(char *name, int class, int priority) {
	int i;
	ppcb newPCB;
	
	if(Find_PCB(name) != NULL){
		printf("Name of process is already taken\n");
		return NULL;
	}
	if(priority < -128 || priority > 127){
		printf("Priority should be in the range -128 to 127\n");
		return NULL;
	}
	if(class != SYSTEM && class != APPLICATION){
		printf("Class must be set to 1 for system or 2 for application\n");
		return NULL;
	}
	
	newPCB = Allocate_PCB();
	strcpy(newPCB->name, name);
	newPCB->priority = priority;
	newPCB->class = class;
	newPCB->state = READY;
	newPCB->memory_size = 0; 
	newPCB->added = clock();
	newPCB->load = NULL;
	newPCB->execute = NULL;
	
	newPCB->stack_top = newPCB->stack_base + PCB_STACK_SIZE - sizeof(context);
	
	for (i = 0; i < PCB_STACK_SIZE; i++) {
		newPCB->stack_base[i] = 0;
	}
	
	return newPCB;
}

/***************************** Utility Functions ******************************/
int numberp(char *string, int length) {
	char current;
	int i;
	int to_return = TRUE;
	
	for (
		i = 0,
		current = string[0];
		to_return == TRUE && i < length && current != '\0';
		i++,
		current = string[i]
	) {
		if (
			(current < '0' || current > '9') &&
			(i != 0 || (current != '+' && current != '-'))
		) {
			to_return = FALSE;
		}
	}
	
	return to_return;
}

int get_string_strip_endline(char *buffer, int length) {
	int error_code;
	int i;
	
	if (
		ERR_SUP_RDFAIL != (
			error_code = sys_req(
				READ,
				TERMINAL,
				buffer,
				&length)
		)
	) {
		for (i = 0; i < length && buffer[i] != '\0'; i++);
		if (buffer[i - 1] == '\n') {
			buffer[i - 1] = '\0';
		}
	}
	
	return error_code;
}

int print_pcb(ppcb to_print) {
	printf(
		"Name: %s, Class: %d, Size: %d, Priority: %d, State: %d Added: %dl\n",
		to_print->name,
		to_print->class,
		to_print->memory_size,
		to_print->priority,
		to_print->state,
		to_print->added
	);
	
	return OK;
}